﻿#region using

using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

#endregion


namespace JupiterLibrary
{
    /// <summary>
    /// Gestiona y encapsula todos los elementos referidos al video
    /// </summary>
    public sealed class Video
    {
        #region Fields

        GraphicsDevice graphicsDevice;

        GameWindow window;

        VDecls vertexDeclarations;

        VertexBuffers vertexBuffers;

        IndexBuffers indexBuffers;

        BasicEffect basicEffect;

        float elapseTime;

        int frameCounter;

        BlendState alphaState;

        #endregion

        public int FPS { get; private set; }

        public UI.GUI UI { get; private set; }

        public Textures Textures { get; private set; }

        public Render Render { get; private set; }

        internal BasicEffect BasicEffect { get { return basicEffect; } }

        internal VDecls VertexDeclarations { get { return vertexDeclarations; } }

        internal VertexBuffers VertexBuffers { get { return vertexBuffers; } }

        internal IndexBuffers IndexBuffers { get { return indexBuffers; } }

        public GraphicsDevice GraphicsDevice { get { return graphicsDevice; } }

        public int BackBufferWidth { get { return graphicsDevice.PresentationParameters.BackBufferWidth; } }

        public int BackBufferHeight { get { return graphicsDevice.PresentationParameters.BackBufferHeight; } }

        public Matrix ViewForSprite { get; private set; }

        public Matrix ProjectionForSprite { get; private set; }
        
        public AlphaTestEffect AlphaTestEffect;

        public RenderTarget2D RenderTarget;

        public Texture2D blackTexture;

        public struct globalRender
        {
            public Camera Camera;

            public Scene Scene;
        } 
        
        /// <summary>
        /// Esta es la información que utiliza el engine para renderizar.
        /// </summary>
        public globalRender GlobalRender;

        public Video(GraphicsDeviceManager manager, GameWindow window)
        {
            bool methodLog = Jupiter.Log.BeginMethod();

            this.window = window;

            // Let the user resize the GameWindow
            window.AllowUserResizing = true;

            // Subscribe to the game window's ClientSizeChanged event.
            window.ClientSizeChanged += new EventHandler<EventArgs>(Window_ClientSizeChanged);

            vertexDeclarations = new VDecls();

            vertexBuffers = new VertexBuffers();

            indexBuffers = new IndexBuffers();

            Textures = new Textures();

            ViewForSprite = Matrix.CreateLookAt(
                    new Vector3(0.0f, 0.0f, 1.0f),
                    Vector3.Zero,
                    Vector3.Up
                    );

            if (methodLog) Jupiter.Log.EndMethod();

        }

        public void LoadContent()
        {
            UI.LoadContent();
        }
        
        void ComputeFPS()
        {
            elapseTime += Jupiter.TotalSecondsElapsed;

            frameCounter++;

            if (elapseTime > 1)
            {
                FPS = frameCounter;
                frameCounter = 0;
                elapseTime = 0;
            }
        }


        public void Dispose()
        {
            vertexDeclarations.Dispose();

            vertexBuffers.Dispose();

            indexBuffers.Dispose();
        }

        /// <summary>
        /// game window's ClientSizeChanged event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Window_ClientSizeChanged(object sender, EventArgs e)
        {
            // XNA has already resized the backbuffer and ZBuffer
            if (graphicsDevice == null) return;

            bool methodLog=Jupiter.Log.BeginMethod();

            Jupiter.Log.WriteLine("RESIZE EVENT");

            Jupiter.Log.WriteLine("BackBufferWidth=" + BackBufferWidth);

            Jupiter.Log.WriteLine("BackBufferHeight=" + BackBufferHeight);

            ProjectionForSprite = Matrix.CreateTranslation(-0.5f, -0.5f, 0) * Matrix.CreateOrthographicOffCenter(
                    0,
                    (float)graphicsDevice.Viewport.Width,
                    (float)graphicsDevice.Viewport.Height,
                    0,
                    0f, 1f);

            if (methodLog) Jupiter.Log.EndMethod();
        }


        public void Initialize(GraphicsDevice graphicsDevice)
        {
            bool methodLog = Jupiter.Log.BeginMethod();

            this.graphicsDevice = graphicsDevice;

            Window_ClientSizeChanged(null, null);

            Color[] color = new Color[1];

            color[0] = Color.Black;

            blackTexture = new Texture2D(graphicsDevice, 1, 1);

            blackTexture.SetData<Color>(color);
                
            // Create the Basic effect

            basicEffect = new BasicEffect(graphicsDevice);

            basicEffect.World = Matrix.Identity;
            basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f),Vector3.Zero,Vector3.Up);
            basicEffect.Projection = Matrix.CreateOrthographicOffCenter(0,
                (float)Jupiter.Video.GraphicsDevice.Viewport.Width,
                (float)Jupiter.Video.GraphicsDevice.Viewport.Height,
                0,1.0f, 10.0f);

            // primitive color
            basicEffect.AmbientLightColor = new Vector3(0.1f, 0.1f, 0.1f);
            basicEffect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
            basicEffect.SpecularColor = new Vector3(0.25f, 0.25f, 0.25f);
            basicEffect.SpecularPower = 5.0f;
            basicEffect.Alpha = 1.0f;

            basicEffect.LightingEnabled = false;

            // Transparencia usando el canal alpha
            alphaState = new BlendState();

            alphaState.AlphaBlendFunction = BlendFunction.Add;
            alphaState.AlphaDestinationBlend = Blend.InverseSourceAlpha;
            alphaState.AlphaSourceBlend = Blend.SourceAlpha;
            alphaState.BlendFactor = Color.White;
            alphaState.ColorBlendFunction = BlendFunction.Add;
            alphaState.ColorDestinationBlend = Blend.InverseSourceAlpha;
            alphaState.ColorSourceBlend = Blend.SourceAlpha;
            alphaState.ColorWriteChannels = ColorWriteChannels.All;
            alphaState.ColorWriteChannels1 = ColorWriteChannels.All;
            alphaState.ColorWriteChannels2 = ColorWriteChannels.All;
            alphaState.ColorWriteChannels3 = ColorWriteChannels.All;

            AlphaTestEffect = new AlphaTestEffect(Jupiter.Video.GraphicsDevice);


            /*
            RasterizerState rasterizerState1 = new RasterizerState();
            rasterizerState1.CullMode = CullMode.None;
            GameEngine.Video.GraphicsDevice.RasterizerState = rasterizerState1;
*/
            Jupiter.Log.WriteLine("BackBufferWidth=" + BackBufferWidth);

            Jupiter.Log.WriteLine("BackBufferHeight=" + BackBufferHeight);

            Jupiter.Log.WriteLine("BackBufferFormat=" + graphicsDevice.PresentationParameters.BackBufferFormat.ToString());

            Render = new JupiterLibrary.Render();

            RenderTarget = new RenderTarget2D(graphicsDevice, BackBufferWidth, BackBufferHeight,false,SurfaceFormat.Color,DepthFormat.Depth24);

            // Create The UI
            UI = new UI.GUI();

            if (methodLog) Jupiter.Log.EndMethod();
        }

        void SetDefaultValuesForDevice()
        {
            graphicsDevice.SetVertexBuffer(null);
            graphicsDevice.Indices = null;

            for (int i = 0; i < 8; i++)
                Jupiter.Video.GraphicsDevice.Textures[i] = null;

            graphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;

            for (int i = 0; i < 8; i++)
            {
                graphicsDevice.SamplerStates[i] = SamplerState.LinearWrap;
            }

            graphicsDevice.BlendFactor = Color.White;

            graphicsDevice.BlendState = BlendState.Opaque;

            graphicsDevice.DepthStencilState = DepthStencilState.Default;
        
        }

        public void SaveBackBuffer(string fullPath)
        {
            Color[] data = new Color[BackBufferWidth * BackBufferHeight];

            graphicsDevice.GetBackBufferData<Color>(data);

            Texture2D texture = new Texture2D(graphicsDevice, BackBufferWidth, BackBufferHeight, false, SurfaceFormat.Color);

            texture.SetData<Color>(data);

            using (Stream stream = File.OpenWrite(fullPath))
            {
                texture.SaveAsPng(stream, texture.Width, texture.Height);
            }
        }

        public void Update()
        {
            ComputeFPS();

            if (UI != null) UI.Update();

        }

        public void DrawBefore()
        {
            // We have to set all the values to default

            SetDefaultValuesForDevice();

        }

        public void SetAlphaBlendState()
        {
            Jupiter.Video.GraphicsDevice.BlendState = alphaState;
        }

        public void DrawBeforeSprites()
        {
            Jupiter.Video.SetDefaultValuesForDevice();

            // No necesitamos Z
            graphicsDevice.DepthStencilState = DepthStencilState.None;

            Jupiter.Video.GraphicsDevice.BlendState = alphaState;

            // Rellenamos los valores básicos del basicEffect que se utiliza para los Sprites

            basicEffect.World = Matrix.Identity;
            basicEffect.View = Jupiter.Video.ViewForSprite;

            basicEffect.Projection = Jupiter.Video.ProjectionForSprite;

            // primitive color
            basicEffect.AmbientLightColor = new Vector3(0.1f, 0.1f, 0.1f);
            basicEffect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
            basicEffect.SpecularColor = new Vector3(0.25f, 0.25f, 0.25f);
            basicEffect.SpecularPower = 5.0f;
            basicEffect.Alpha = 1.0f;

            basicEffect.LightingEnabled = false;

            basicEffect.VertexColorEnabled = true;

        }

        public void DrawAfter()
        {
            // We have to set all the values to default
            
            SetDefaultValuesForDevice();

            // if (UI != null) UI.Draw();

        }

    }
}
